package cn.deschen.eshop.auth.validate;

import cn.deschen.eshop.auth.enums.ValidateCodeTypeEnum;
import cn.deschen.eshop.auth.properties.BaseCodeProperties;
import cn.deschen.eshop.auth.properties.SecurityProperties;
import cn.deschen.eshop.auth.validate.cache.ValidateCodeCache;
import cn.deschen.eshop.common.enums.GlobalEnum;
import cn.deschen.eshop.common.exception.GlobalException;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author hanbin_chen
 * @Description 通用的2验证码处理
 * @DateTime 2021/1/14 15:13
 * @Version V1.0.0
 */
@Slf4j
public abstract class AbstractValidateCodeProcessor implements ValidateCodeProcessor {

    @Autowired
    private ValidateCodeCache codeCache;

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private Map<String, ValidateCodeGenerator> validateCodeGeneratorMap;

    private Map<ValidateCodeTypeEnum, BaseCodeProperties> codeTypeMap = new HashMap<>(16);

    @PostConstruct
    public void initialize(){
        codeTypeMap.put(ValidateCodeTypeEnum.IMAGE, securityProperties.getCode().getImage());
        codeTypeMap.put(ValidateCodeTypeEnum.SMS, securityProperties.getCode().getSms());
    }

    @Override
    public void createCode(ServletWebRequest request, ValidateCodeTypeEnum codeTypeEnum) {
        // 生成验证码
        ValidateCode code = generateCode(request);
        // 保存验证码
        saveCode(request, code.getCode(), codeTypeEnum);
        // 发送验证码
        sendCode(request, code);
    }

    /**
     * 发送验证码
     * @param request 请求体，用于响应发送验证码
     * @param code 验证码实体类
     */
    protected abstract void sendCode(ServletWebRequest request, ValidateCode code);

    /**
     * 生成验证码
     *
     * @param request
     * @return
     */
    private ValidateCode generateCode(ServletWebRequest request) {
        // 获取验证码
        ValidateCodeGenerator validateCodeGenerator = getValidateCodeGenerator();
        // 生成验证码
        ValidateCode validateCode = validateCodeGenerator.generate(request);
        return validateCode;
    }

    /**
     * 获取验证码生成器
     * 验证码处理类是 {type}+ValidateCodeProcessor组成的，而验证码生成类是{type}+ValidateCodeGenerator
     * 先获取type，拼接验证码生成类的类名获取其类
     * @return
     */
    private ValidateCodeGenerator getValidateCodeGenerator() {
        String typeName = StrUtil.subBefore(
                getClass().getSimpleName(), ValidateCodeProcessor.class.getSimpleName(), true);
        // 因为typeName是首字母大写，而bean命名首字母小写，要先处理
        String validateCodeGeneratorName = typeName.toLowerCase() + ValidateCodeGenerator.class.getSimpleName();
        return validateCodeGeneratorMap.get(validateCodeGeneratorName);
    }

    /**
     * 保存验证码
     * @param code 验证码的内容
     * @param codeTypeEnum
     */
    private void saveCode(ServletWebRequest request, String code, ValidateCodeTypeEnum codeTypeEnum) {
        // 获取请求的校验的验证码
        BaseCodeProperties codeProperties = codeTypeMap.get(codeTypeEnum);
        codeCache.setCode(request, code, codeTypeEnum, codeProperties.getTimeout());
    }

    @Override
    public void validateCode(ServletWebRequest request, ValidateCodeTypeEnum codeTypeEnum) {
        // 获取请求的校验的验证码
        BaseCodeProperties codeProperties = codeTypeMap.get(codeTypeEnum);
        String codeInRequest;
        try {
            codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),
                    codeProperties.getReqCodeKey());
        } catch (ServletRequestBindingException e) {
            log.error("【权限模块】获取验证码失败");
            throw new GlobalException(GlobalEnum.OPERATION_ERROR, "获取验证码的值失败");
        }

        // 判断请求中验证码是否有值
        if (null == codeInRequest || "".equals(codeInRequest)) {
            log.error("【权限模块】验证码值为空");
            throw new GlobalException(GlobalEnum.EXIST_ERROR, "验证码值为空");
        }

        // 获取缓存的验证码
        String codeInCache = codeCache.getCode(request, codeTypeEnum);

        // 判断缓存验证码是否存在
        if (null == codeInCache) {
            log.error("【权限模块】缓存的验证码值不存在");
            throw new GlobalException(GlobalEnum.EXIST_ERROR, "缓存的验证码值不存在");
        }

        // 判断缓存的验证码与请求的验证码是否匹配
        if (!codeInCache.equals(codeInRequest)) {
            log.error("【权限模块】验证码不匹配");
            throw new GlobalException(GlobalEnum.OPERATION_ERROR, "验证码不匹配");
        }

        codeCache.removeCode(request, codeTypeEnum);
    }
}
